<!doctype html>
<html lang="en">
<head>
  <meta charset="utf-8" />
  <meta name="viewport" content="width=device-width, initial-scale=1" />
  <title>MicroPython WASM (ESM) — minimal demo</title>
  <style>
    body { font-family: system-ui, -apple-system, Segoe UI, Roboto, Helvetica, Arial, sans-serif; margin: 2rem; }
    h1 { font-size: 1.25rem; margin-bottom: 0.5rem; }
    textarea { width: 100%; height: 10rem; font-family: ui-monospace, SFMono-Regular, Menlo, Consolas, "Liberation Mono", monospace; }
    pre { background: #0b0b0c; color: #e6e6e6; padding: 1rem; border-radius: 8px; overflow:auto; }
    .row { display: flex; gap: .5rem; align-items: center; margin: .5rem 0 1rem; }
    button { padding: .5rem .9rem; border-radius: 8px; border: 1px solid #ccc; background: #fff; cursor: pointer; }
    button[disabled] { opacity: .6; cursor: wait; }
    small { color: #666; }
  </style>
</head>
<body>
  <h1>MicroPython from CDN (WASM) — super simple</h1>
  <p>This page loads the <code>@micropython/micropython-webassembly-pyscript</code> ESM build and executes Python in your browser via WebAssembly.</p>

  <ol>
    <li>Type some Python below (defaults to <code>print('hello world')</code>).</li>
    <li>Click <strong>Run</strong> or run the built‑in <strong>Tests</strong>.</li>
  </ol>

  <textarea id="code">print('hello world')
</textarea>
  <div class="row">
    <button id="run" disabled>Initializing…</button>
    <button id="tests" disabled>Run tests</button>
    <small id="status">Loading MicroPython (WASM)…</small>
  </div>
  <pre id="out" aria-live="polite"></pre>

  <script type="module">
    // Use the official browser-first ESM loader. It fetches the sibling .wasm automatically.
    import * as mpjs from 'https://cdn.jsdelivr.net/npm/@micropython/micropython-webassembly-pyscript@1.26.0/micropython.mjs';

    const runBtn = document.getElementById('run');
    const testBtn = document.getElementById('tests');
    const out = document.getElementById('out');
    const status = document.getElementById('status');
    const codeEl = document.getElementById('code');

    function log(line = "") { out.textContent += line + "\n"; }

    let mp;
    let activeCapture = null;

    function pushOutput(kind, text) {
      if (!text) return;
      if (activeCapture) {
        activeCapture[kind] += text;
      } else {
        (kind === 'stderr' ? console.error : console.log)(text);
      }
    }

    async function runWithCapture(src) {
      activeCapture = { stdout: '', stderr: '' };
      let error;
      try {
        await mp.runPythonAsync(src.endsWith('\n') ? src : `${src}\n`);
      } catch (err) {
        error = err;
      }
      const result = activeCapture;
      activeCapture = null;
      if (error && typeof error === 'object') {
        error.stdout = result.stdout;
        error.stderr = result.stderr;
      }
      if (error) throw error;
      return result;
    }

    try {
      mp = await mpjs.loadMicroPython({
        linebuffer: true,
        stdout: (text) => pushOutput('stdout', text),
        stderr: (text) => pushOutput('stderr', text),
      });
      status.textContent = 'Ready.';
      runBtn.textContent = 'Run Python';
      runBtn.disabled = false;
      testBtn.disabled = false;
    } catch (e) {
      status.textContent = 'Failed to initialize MicroPython';
      log(String(e && e.message ? e.message : e));
      throw e;
    }

    async function runAndCapture(pySrc) {
      const src = pySrc.endsWith('\n') ? pySrc : pySrc + '\n';
      const current = {
        stdout: attachCapture(),
        stderr: attachCapture(),
      };
      streamCapture.stdout = current.stdout;
      streamCapture.stderr = current.stderr;
      let error;
      try {
        await mp.runPythonAsync(src);
      } catch (err) {
        error = err;
      } finally {
        streamCapture.stdout = null;
        streamCapture.stderr = null;
      }

      const result = mergeResult(current);
      if (error && typeof error === 'object') {
        error.stdout = result.stdout;
        error.stderr = result.stderr;
      }
      if (error) throw error;
      return result;
    }

    runBtn.addEventListener('click', async () => {
      runBtn.disabled = true;
      status.textContent = 'Running…';
      out.textContent = '';
      try {
        const { stdout, stderr } = await runWithCapture(codeEl.value);
        if (stdout) log(stdout.trimEnd());
        if (stderr) log(stderr.trimEnd());
      } catch (err) {
        if (err?.stdout) log(String(err.stdout).trimEnd());
        if (err?.stderr) log(String(err.stderr).trimEnd());
        log(String(err?.message ?? err));
      } finally {
        status.textContent = 'Ready.';
        runBtn.disabled = false;
      }
    });

    // --- Tests (existing preserved semantics; extra tests appended earlier) ---
    testBtn.addEventListener('click', async () => {
      testBtn.disabled = true;
      out.textContent = '';
      status.textContent = 'Running tests…';

      const results = [];
      const record = (name, pass, info = '') => {
        results.push({ name, pass, info });
        log(`${pass ? '✔' : '✖'} ${name}${info ? ' — ' + info : ''}`);
      };

      try {
        // (1) prints hello world — DO NOT MODIFY (existing test)
        {
          const { stdout } = await runWithCapture("print('hello world')\n");
          record('prints hello world', stdout.trim() === 'hello world', `got: ${JSON.stringify(stdout.trim())}`);
        }
        // (2) arithmetic — DO NOT MODIFY (existing test)
        {
          const { stdout } = await runWithCapture('print(1+2)\n');
          record('arithmetic 1+2 == 3', stdout.trim() === '3', `got: ${JSON.stringify(stdout.trim())}`);
        }
        // (3) state persists — DO NOT MODIFY (existing test)
        {
          await mp.runPythonAsync('x=41\n');
          const { stdout } = await runWithCapture('print(x+1)\n');
          record('state persists across runs', stdout.trim() === '42', `got: ${JSON.stringify(stdout.trim())}`);
        }
        // (4) exceptions propagate — DO NOT MODIFY (existing test)
        {
          let raised = false;
          try { await mp.runPythonAsync("raise ValueError('boom')\n"); } catch (_) { raised = true; }
          record('exceptions propagate to JS', raised, raised ? '' : 'expected rejection');
        }
        // (5) NEW — stdlib math sanity
        {
          const { stdout } = await runWithCapture('import math\nprint(math.sqrt(9))\n');
          record('math.sqrt works', stdout.trim().split(/\s+/).pop() === '3.0', `got: ${JSON.stringify(stdout.trim())}`);
        }
        // (6) NEW — unicode output
        {
          const { stdout } = await runWithCapture("print('μPython')\n");
          record('unicode print', stdout.trim().endsWith('μPython'), `got: ${JSON.stringify(stdout.trim())}`);
        }
      } catch (e) {
        record('test harness crashed', false, String(e));
      } finally {
        const passed = results.filter(r => r.pass).length;
        const total = results.length;
        status.textContent = `Tests complete — ${passed}/${total} passed`;
        testBtn.disabled = false;
      }
    });
  </script>
</body>
</html>
